﻿using BitConverter;
using JianZhangTool.tool;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using WebSocketSharp;
using zlib;
using Logger = JianZhangTool.log.Logger;

namespace JianZhangTool.net
{
    class BiliDanmuConnecterWS
    {
        /// <summary>
        /// 包参数magic
        /// </summary>
        private const short bili_magic = 16;
        /// <summary>
        /// 包参数ver
        /// </summary>
        private const short bili_protocol_ver = 1;
        /// <summary>
        /// 包参数param
        /// </summary>
        private const int bili_param = 1;

        /// <summary>
        /// WS长连接
        /// </summary>
        private WebSocket client;

        /// <summary>
        /// 房间号
        /// </summary>
        private int roomId;
        /// <summary>
        /// Token
        /// </summary>
        private string token;

        /// <summary>
        /// 是否连接中
        /// </summary>
        private bool connected;

        public BiliDanmuConnecterWS(int roomId, string token)
        {
            this.roomId = roomId;
            this.token = token;
        }

        public async Task<bool> connect()
        {
            try
            {
                string url = "ws://broadcastlv.chat.bilibili.com:2244/sub";
                client = new WebSocket(url);
                client.OnMessage += receive_message;
                client.Connect();
                connected = true;
                if (await join_chat_channel())
                {
                    _ = heart_beat_loop();
                    _ = analysis_message_loop();
                }
            }
            catch (Exception)
            {
                return false;
            }
            return true;
        }

        /// <summary>
        /// 断开连接
        /// </summary>
        /// <param name="inside"></param>
        public async void disconnect(bool inside = false)
        {
            connected = false;
            // 反馈给主面板
            await Main.Instance.trigger_danmu_disconnect(inside);
        }

        /// <summary>
        /// 请求加入弹幕频道
        /// </summary>
        /// <returns></returns>
        private async Task<bool> join_chat_channel()
        {
            JObject jo = new JObject();
            jo["uid"] = 0;
            jo["roomid"] = roomId;
            jo["protover"] = 2;
            jo["platform"] = "web";
            jo["clientver"] = "1.10.3";
            jo["type"] = 2;
            jo["key"] = token;
            await send_socket_data_async(7, jo.ToString());
            return true;
        }

        /// <summary>
        /// 心跳包
        /// </summary>
        /// <returns></returns>
        private async Task heart_beat_loop()
        {
            try
            {
                while (connected)
                {
                    await send_socket_data_async(2, "");
                    await Task.Delay(30000);
                }
            }
            catch (Exception)
            {
                disconnect(true);
            }
        }

        /// <summary>
        /// 缓存的BUFF
        /// </summary>
        private List<byte[]> buffers = new List<byte[]>();
        /// <summary>
        /// 缓存锁
        /// </summary>
        private readonly object buffers_lock = new object();

        /// <summary>
        /// 获取信息循
        /// </summary>
        /// <returns></returns>
        private void receive_message(object sender, MessageEventArgs e)
        {
            try
            {
                byte[] buffer = e.RawData;
                /*byte[] buffer = new byte[e.RawData.Length];
                Array.Copy(e.RawData, buffer, e.RawData.Length);*/
                lock (buffers_lock)
                {
                    buffers.Add(buffer);
                }
            }
            catch (Exception)
            {
                if (connected)
                {
                    disconnect(true);
                }
            }
        }

        /// <summary>
        /// 上个包冗余的字节
        /// </summary>
        private byte[] dirty_bytes = null;

        /// <summary>
        /// 循环解析
        /// </summary>
        private async Task analysis_message_loop()
        {
            while (connected)
            {
                await Task.Delay(1);
                if (buffers.Count == 0)
                {
                    continue;
                }
                List<byte[]> templist = null;
                lock (buffers_lock)
                {
                    templist = buffers;
                    buffers = new List<byte[]>();
                }
                foreach (byte[] buffer in templist)
                {
                    BiliDanmuPacket packet = BiliDanmuPacket.FromBuffer(buffer);
                    if (packet.length < 16)
                    {
                        throw new NotSupportedException("协议失败: (L:" + packet.length + ")");
                    }
                    var main_length = packet.length - 16;
                    if (main_length == 0)
                    {
                        // 没有内容
                        continue;
                    }
                    // 区分压缩类型
                    if (packet.version == 2 && packet.action == 5)
                    {
                        // 处理压缩消息
                        try
                        {
                            MemoryStream compressed = new MemoryStream(buffer, 16, main_length);
                            MemoryStream decompressed = new MemoryStream();
                            ZOutputStream deflate_stream = new ZOutputStream(decompressed);
                            Tools.CopyStream(compressed, deflate_stream);
                            deflate_stream.finish();
                            const BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance;
                            Type type = deflate_stream.GetType();
                            FieldInfo property = type.GetField("buf", flags);
                            byte[] src = (byte[])property.GetValue(deflate_stream);
                            byte[] out_buffer = new byte[src.Length];
                            Array.Copy(src, out_buffer, src.Length);
                            out_buffer = Tools.bytesTrimEnd(out_buffer);
                            if (dirty_bytes != null)
                            {
                                byte[] temp = new byte[dirty_bytes.Length + out_buffer.Length];
                                Buffer.BlockCopy(dirty_bytes, 0, temp, 0, dirty_bytes.Length);
                                Buffer.BlockCopy(out_buffer, 0, temp, dirty_bytes.Length, out_buffer.Length);
                                out_buffer = temp;
                                dirty_bytes = null;
                            }
                            List<string> cuts = cut_byte_array(out_buffer);
                            if (cuts != null)
                            {
                                for (int i = 0; i < cuts.Count; i++)
                                {
                                    bool success = true;
                                    string str = cuts[i];
                                    int st = str.IndexOf('{');
                                    int ed = str.LastIndexOf('}');
                                    if (st == -1 || ed == -1)
                                    {
                                        success = false;
                                    }
                                    if (success)
                                    {
                                        string json_str = str.Substring(st, ed - st + 1);
                                        success = receive_danmu_message(packet.action, json_str);
                                    }
                                    if (!success)
                                    {
                                        // 如果最后一项被切割 保留在dirty中
                                        if (i == cuts.Count - 1)
                                        {
                                            dirty_bytes = Encoding.UTF8.GetBytes(str);
                                        }
                                        else
                                        {
                                            // 非最后一项则记录dirty
                                            Logger.log("dirty", "[" + i + "][" + DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + "]" + str);
                                        }
                                    }
                                }
                            }
                        }
                        catch (Exception e)
                        {
                            Logger.log("error", e.StackTrace);
                        }
                    }
                    else
                    {
                        // 处理非压缩信息
                        // 切掉头部信息
                        MemoryStream temp_ms = new MemoryStream(buffer, 16, main_length);
                        byte[] bs = temp_ms.ToArray();
                        // 处理普通消息
                        receive_danmu_message(packet.action, Encoding.UTF8.GetString(bs));
                    }
                }
            }
        }

        /// <summary>
        /// 发送信息
        /// </summary>
        /// <param name="action"></param>
        /// <param name="body"></param>
        /// <returns></returns>
        private async Task send_socket_data_async(int action, string body = "")
        {
            byte[] body_bytes = Encoding.UTF8.GetBytes(body);
            // 数据总长
            int length = body_bytes.Length + 16;
            // 消息体
            byte[] buffer = new byte[length];
            // 写入
            using (MemoryStream ms = new MemoryStream(buffer))
            {
                var b = EndianBitConverter.BigEndian.GetBytes(buffer.Length);
                await ms.WriteAsync(b, 0, 4);
                b = EndianBitConverter.BigEndian.GetBytes(bili_magic);
                await ms.WriteAsync(b, 0, 2);
                b = EndianBitConverter.BigEndian.GetBytes(bili_protocol_ver);
                await ms.WriteAsync(b, 0, 2);
                b = EndianBitConverter.BigEndian.GetBytes(action);
                await ms.WriteAsync(b, 0, 4);
                b = EndianBitConverter.BigEndian.GetBytes(bili_param);
                await ms.WriteAsync(b, 0, 4);
                if (body_bytes.Length > 0)
                {
                    await ms.WriteAsync(body_bytes, 0, body_bytes.Length);
                }
                client.Send(ms.ToArray());
            }
        }

        /// <summary>
        /// 收到直播间弹幕信息
        /// </summary>
        /// <param name="action"></param>
        /// <param name="buffer"></param>
        private bool receive_danmu_message(int action, string json_str)
        {
            switch (action)
            {
                case 5:
                    {
                        JObject bili_msg_json;
                        try
                        {
                            bili_msg_json = JObject.Parse(json_str);
                        }
                        catch (Exception)
                        {
                            return false;
                        }
                        if (bili_msg_json != null)
                        {
                            BiliDanmuMessage.OnMsg(bili_msg_json);
                        }
                        break;
                    }
                default:
                    {
                        break;
                    }
            }
            return true;
        }

        /// <summary>
        /// 切割字节数组
        /// </summary>
        /// <param name="array"></param>
        /// <returns></returns>
        private List<string> cut_byte_array(byte[] array)
        {
            if (array == null || array.Length == 0)
            {
                return new List<string>();
            }
            List<string> ret = new List<string>();
            int start = 0;
            for (int i = 0; i < array.Length - 12; i++)
            {
                // 截取头
                if (array[i] == 0 && array[i + 1] == 16 && array[i + 2] == 0 && array[i + 3] == 0 && array[i + 4] == 0 && array[i + 5] == 0 && array[i + 6] == 0
                    && array[i + 7] == 5 && array[i + 8] == 0 && array[i + 9] == 0 && array[i + 10] == 0 && array[i + 11] == 0)
                {
                    int end = i - 5;
                    if (end > start)
                    {
                        string str = Encoding.UTF8.GetString(array, start, end - start + 1);
                        ret.Add(str);
                    }
                    start = i + 12;
                }
            }
            if (start < array.Length)
            {
                string str = Encoding.UTF8.GetString(array, start, array.Length - start);
                ret.Add(str);
            }
            return ret;
        }
    }
}
